//=============================================================================
// Brief   : MIH User Example
// Authors : Bruno Santos <bsantos@av.it.pt>
//------------------------------------------------------------------------------
// ODTONE - Open Dot Twenty One
//
// Copyright (C) 2009-2011 Universidade Aveiro
// Copyright (C) 2009-2011 Instituto de Telecomunicações - Pólo Aveiro
//
// This software is distributed under a license. The full license
// agreement can be found in the file LICENSE in this distribution.
// This software may not be copied, modified, sold or distributed
// other than expressed in the named license agreement.
//
// This software is distributed without any warranty.
//==============================================================================

#include <odtone/base.hpp>
#include <odtone/debug.hpp>
#include <odtone/logger.hpp>
#include <odtone/mih/request.hpp>
#include <odtone/mih/response.hpp>
#include <odtone/mih/indication.hpp>
#include <odtone/mih/confirm.hpp>
#include <odtone/mih/tlv_types.hpp>
#include <odtone/sap/user.hpp>
#include <boost/utility.hpp>
#include <boost/bind.hpp>
#include <iostream>

#include <pthread.h>	//yan-21/02/2012

///////////////////////////////////////////////////////////////////////////////

namespace po = boost::program_options;

using odtone::uint;
using odtone::ushort;

odtone::logger log_("mih_usr", std::cout);

///////////////////////////////////////////////////////////////////////////////
//yan-start-23/02/2012
class link_info {
public:
	link_info() {}
	link_info(odtone::mih::link_tuple_id &li, odtone::mih::octet_string &name)
		: _lid(li), _ifname(name)
	{}

	void lid(odtone::mih::link_tuple_id &li) {_lid = li;}
	void ifname(odtone::mih::octet_string &name) {_ifname = name;}

	odtone::mih::link_tuple_id lid() {return _lid;}
	odtone::mih::octet_string ifname() {return _ifname;}
private:
	odtone::mih::link_tuple_id _lid;
	odtone::mih::octet_string _ifname;
};

odtone::mih::octet_string mih_get_ifname(odtone::mih::link_tuple_id &lid)
{
	odtone::mih::octet_string cmd;
	cmd = (boost::get<odtone::mih::mac_addr>(lid.addr)).address();
	if (cmd == "")
	{
		cmd = "ifconfig | grep \"Point-to-Point Protocol";
	}
	else
	{
		cmd = "ifconfig | grep \"HWaddr " + cmd;
	}
	cmd += "\" | awk \'{print $1}\'";
	FILE *fp = popen(cmd.c_str(), "r");
	char name[20];
	if (fp != NULL)
	{
		if (fgets(name, 20, fp) != NULL)
		{
			pclose(fp);
			int i;
			for (i = 0; name[i] != '\n' && name[i] != '\0'; ++i);
			name[i] = '\0';
			return odtone::mih::octet_string(name);
		}
		pclose(fp);
	}
	return "";
}
//yan-end

class mih_user : boost::noncopyable {
public:
	mih_user(const odtone::mih::config& cfg, boost::asio::io_service& io);
	~mih_user();

	//yan-start-22/02/2012
	void list_links();
	odtone::mih::link_type get_link_type(unsigned int i);
	odtone::mih::octet_string get_link_addr(unsigned int i);
	odtone::mih::octet_string get_link_name(unsigned int i);
	//yan-end

protected:
	void event_handler(odtone::mih::message& msg, const boost::system::error_code& ec);
	void capability_discover_confirm(odtone::mih::message& msg, const boost::system::error_code& ec);
	void event_subscribe_response(odtone::mih::message& msg, const boost::system::error_code& ec);

private:
	odtone::sap::user _mihf;
	odtone::mih::id   _mihfid;

	std::vector<link_info> _mihlnks;
	//odtone::mih::link_tuple_id _mihlid[256];	//yan-22/02/2012
	//int _num_links;	//yan-22/02/2012
};

mih_user::mih_user(const odtone::mih::config& cfg, boost::asio::io_service& io)
	: _mihf(cfg, io, boost::bind(&mih_user::event_handler, this, _1, _2))
{

	odtone::mih::message m;

	m << odtone::mih::indication(odtone::mih::indication::user_register)
	    & odtone::mih::tlv_mbb_handover_support(true);

	_mihf.sync_send(m);

	// Wait a little after register
	sleep(1);

	odtone::mih::message msg;

	odtone::mih::octet_string destination = cfg.get<odtone::mih::octet_string>(odtone::sap::kConf_MIH_SAP_dest);
	// check if the user passed a --dest option in the command
	// line, if not then use the default MIHF identifier
	if (destination.length() == 0)
		destination = cfg.get<odtone::mih::octet_string>(odtone::sap::kConf_MIHF_Id);

	_mihfid.assign(destination.c_str());

	//
	// Let's fire a capability discover request to get things moving
	//
	msg << odtone::mih::request(odtone::mih::request::capability_discover, _mihfid);

	_mihf.async_send(msg, boost::bind(&mih_user::capability_discover_confirm, this, _1, _2));

	log_(0, "MIH-User has sent a Capability_Discover.request towards its local MIHF");

	//_num_links = 0;	//yan-22/02/2012	
}

mih_user::~mih_user()
{
}

void mih_user::event_handler(odtone::mih::message& msg, const boost::system::error_code& ec)
{
	//yan-start-17/02/2012
	odtone::mih::message m;
	m << odtone::mih::indication(odtone::mih::indication::link_get_parameters);
	_mihf.sync_send(m);
	//yan-end

	if (ec) {
		log_(0, __FUNCTION__, " error: ", ec.message());
		return;
	}

	//yan-start-17/02/2012
	odtone::mih::link_tuple_id lid;
	odtone::mih::octet_string ifname;
	std::vector<link_info>::iterator it;
	bool exist = false;

	msg >> odtone::mih::indication()
		& odtone::mih::tlv_link_identifier(lid);

	switch (msg.mid()) {
	case odtone::mih::indication::link_up:
		log_(0, "MIH-User has received a local event \"link_up\"");
		//log_(0, lid.addr);
		//_mihlid[_num_links] = lid;
		//++_num_links;
		exist = false;
		for (it = _mihlnks.begin(); it != _mihlnks.end(); ++it)
                {
                        if (it->lid() == lid)
                        {
                                exist = true;
                                break;
                        }
                }
		if (!exist)
		{
			ifname = mih_get_ifname(lid);
			_mihlnks.push_back(link_info(lid, ifname));
		}
		break;

	case odtone::mih::indication::link_down:
		log_(0, "MIH-User has received a local event \"link_down\"");
		for (it = _mihlnks.begin(); it != _mihlnks.end(); ++it)
		{
			if (it->lid() == lid)
			{
				_mihlnks.erase(it);
				break;
			}
		}
		/*int i;
		for (i = 0; i < _num_links; ++i)
		{
			if (_mihlid[i] == lid)
			{
				--_num_links;
				break;
			}
		}
		for (i = i + 1; i <= _num_links; ++i)
		{
			_mihlid[i-1] = _mihlid[i];
		}*/
		break;
	//yan-end

	case odtone::mih::indication::link_detected:
		log_(0, "MIH-User has received a local event \"link_detected\"");
		break;

	case odtone::mih::indication::link_going_down:
		log_(0, "MIH-User has received a local event \"link_going_down\"");
		break;

	case odtone::mih::indication::link_handover_imminent:
		log_(0, "MIH-User has received a local event \"link_handover_imminent\"");
		break;
	case odtone::mih::indication::link_handover_complete:
		log_(0, "MIH-User has received a local event \"link_handover_complete\"");
		break;
	}
}

void mih_user::capability_discover_confirm(odtone::mih::message& msg, const boost::system::error_code& ec)
{
	if (ec) {
		log_(0, __FUNCTION__, " error: ", ec.message());
		return;
	}

	odtone::mih::status st;
	boost::optional<odtone::mih::net_type_addr_list> ntal;
	boost::optional<odtone::mih::event_list> evt;

	msg >> odtone::mih::confirm()
		& odtone::mih::tlv_status(st)
		& odtone::mih::tlv_net_type_addr_list(ntal)
		& odtone::mih::tlv_event_list(evt);

	log_(0, "MIH-User has received a Capability_Discover.response with status ",
			st.get(), " and the following capabilities:");

	if (ntal) {
		for (odtone::mih::net_type_addr_list::iterator i = ntal->begin(); i != ntal->end(); ++i)
			log_(0,  *i);

	} else {
		log_(0,  "none");
	}

	//
	// event subscription
	//
	// For every interface the MIHF sent in the
	// Capability_Discover.response send an Event_Subscribe.request
	// for all availabe events
	//
	if (ntal && evt) {
		for (odtone::mih::net_type_addr_list::iterator i = ntal->begin(); i != ntal->end(); ++i) {
			odtone::mih::message req;
			odtone::mih::link_tuple_id li;

			if (i->nettype.link.which() == 1)
				{
					li.addr = i->addr;
					li.type = boost::get<odtone::mih::link_type>(i->nettype.link);
					//yan-start-23/02/2012
					//_mihlid[_num_links] = li;
					//++_num_links;
					odtone::mih::octet_string ifname = mih_get_ifname(li);
					if (ifname != "")
					{
						_mihlnks.push_back(link_info(li, ifname));
					}
					//yan-end
					req << odtone::mih::request(odtone::mih::request::event_subscribe, _mihfid)
						& odtone::mih::tlv_link_identifier(li)
						& odtone::mih::tlv_event_list(evt);

					_mihf.async_send(req, boost::bind(&mih_user::event_subscribe_response, this, _1, _2));

					log_(0, "MIH-User has sent Event_Subscribe.request");
				}
		}
	}
}

void mih_user::event_subscribe_response(odtone::mih::message& msg, const boost::system::error_code& ec)
{
	log_(0, __FUNCTION__, "(", msg.tid(), ")");

	if (ec) {
		log_(0, __FUNCTION__, " error: ", ec.message());
		return;
	}

	odtone::mih::status st;

	msg >> odtone::mih::response()
		& odtone::mih::tlv_status(st);

	log_(0, "status: ", st.get());
}

//yan-start-21/02/2012
void mih_user::list_links()
{
	//log_(0, "No\tType\tAddress\n");
	std::cout << "No.\tName\tAddress" << std::endl;
	for (unsigned int i = 0; i < _mihlnks.size(); ++i)
	{
		//log_(0, i, '\t', _mihlid[i].type, '\t');
		std::cout << (i + 1) << '\t' << get_link_name(i);
		/*
		switch (get_link_type(i).get())
		{
		case odtone::mih::link_type_gsm: std::cout << "GSM\t";	break;
		case odtone::mih::link_type_gprs: std::cout << "GPRS\t";	break;
		case odtone::mih::link_type_edge: std::cout << "EDGE\t";	break;

		case odtone::mih::link_type_ethernet: std::cout << "Ethernet";	break;

		case odtone::mih::link_type_wireless_other: std::cout << "Wireless";	break;
		case odtone::mih::link_type_802_11:
			if (get_link_addr(i) == "")
			{
				std::cout << "LTE\t";
			}
			else
			{
				std::cout << "802.11\t";
			}
			break;

		case odtone::mih::link_type_cdma2000: std::cout << "CDMA2000";	break;
		case odtone::mih::link_type_umts: std::cout << "UMTS\t";	break;
		case odtone::mih::link_type_cdma2000_hrpd: std::cout << "CDMA2000-HRPD";	break;

		case odtone::mih::link_type_802_16: std::cout << "802.16\t";	break;
		case odtone::mih::link_type_802_20: std::cout << "802.20\t";	break;
		case odtone::mih::link_type_802_22: std::cout << "802.22\t";	break;
		}*/
 		std::cout << '\t' << get_link_addr(i) << std::endl;
	}
}
//yan-end

//yan-start-21/02/2012
odtone::mih::link_type mih_user::get_link_type(unsigned int i)
{
	if (i < 0 || i >= _mihlnks.size())
	{
		return (odtone::mih::link_type());
	}
	//return _mihlid[i].type;
	return _mihlnks.at(i).lid().type;
}
//yan-end

//yan-start-21/02/2012
odtone::mih::octet_string mih_user::get_link_addr(unsigned int i)
{
	if (i < 0 || i >= _mihlnks.size())
	{
		return "";
	}
	//return (boost::get<odtone::mih::mac_addr>(_mihlid[i].addr)).address();
	return (boost::get<odtone::mih::mac_addr>(_mihlnks.at(i).lid().addr)).address();
}
//yan-end

//yan-start-23/02/2012
odtone::mih::octet_string mih_user::get_link_name(unsigned int i)
{
	if (i < 0 || i >= _mihlnks.size())
	{
		return "";
	}
	//return (boost::get<odtone::mih::mac_addr>(_mihlid[i].addr)).address();
	return _mihlnks.at(i).ifname();
}
//yan-end

//yan-start-23/02/2012
int mip6_pref(odtone::mih::octet_string ifname)
{
	int sockfd;
        int n, i;
        struct sockaddr_in serv_addr;
	odtone::mih::octet_string cmd;
        char buf[256];

        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
                perror("MIPv6: Failed to create socket");
                return -1;
        }

        serv_addr.sin_family = AF_INET;
        inet_aton("127.0.0.1", &serv_addr.sin_addr);
        serv_addr.sin_port = htons(7777);

        if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
        {
                perror("MIPv6: Failed to establish connection");
                return -1;
        }

	cmd = "pref " + ifname + "\n";
	n = read(sockfd, buf, 255);
        n = write(sockfd, cmd.c_str(), cmd.length());
        if (n < 0)
        {
                perror("MIPv6: Failed to send command via socket");
		return -1;
        }
        n = read(sockfd, buf, 255);
	if (n < 0)
	{
		perror("MIPv6: Failed to receive result");
		return -1;
	}
	for (i = 0; i < n && buf[i] != '\n'; ++i);
	buf[i] = 0;
	std::cout << "MIPv6: " << buf << std::endl;

        close(sockfd);
	return 0;
}
//yan-end

//yan-start-21/02/2012
void *keyboard_handler(void *arg)
{
	int i;
	char buf[256];
	mih_user *p_usr = (mih_user *) arg;
	odtone::mih::link_type type;
	odtone::mih::octet_string cmd, ifname;

	sleep(1);
	while (true)
	{
		std::cout << "> ";
		std::cin.getline(buf, 256);

		FILE *fp = popen("netstat -rn | grep ^0.0.0.0 | awk \'{print $8}\'", "r");	//ipv4
		char temp[20];

		switch (buf[0])
		{
		case 'l':
			p_usr->list_links();
			break;
		case 'c':
			i = atoi(buf + 1) - 1;
			ifname = p_usr->get_link_name(i);
			if (ifname == "")
			{
				std::cout << "Number out of range\n> " << std::flush;
			}
			else
			{	//ipv4
				cmd = "sudo route add -net 0.0.0.0 netmask 0.0.0.0 dev " + ifname;
				if (fgets(temp, 20, fp) != NULL)
				{
					int j;
					for (j = 0; temp[j] != '\n' && temp[j] != '\0'; ++j);
					temp[j] = '\0';
					//std::cout << ifname.c_str() << ' ' << temp << std::endl;
					if (!strcmp(ifname.c_str(), temp)) //same interface, no need to change
					{
						std::cout << "HIP: Already on " << temp << std::endl;
						break;
					}
					cmd = cmd + " && sudo route del default dev " + temp ;
				}
				//std::cout << cmd << std::endl;
				system(cmd.c_str());
				std::cout << "HIP: Interface changed" << std::endl;	

				//ipv6
				/* disable ipv6
				cmd = "cat /proc/sys/net/ipv6/conf/" + ifname + "/disable_ipv6";
				fp = popen(cmd.c_str(), "r");
				if (fgets(temp, 20, fp) != NULL && temp[0] == '0')
				{
						std::cout << "Already on " << ifname << std::endl;
                                                break;
                                }
				system("sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1");
				cmd = "sudo sysctl -w net.ipv6.conf." + ifname + ".disable_ipv6=0";
				system(cmd.c_str());
				std::cout << "Handover complete" << std::endl;
				*/
				mip6_pref(ifname);
			}
			break;
		case 'q':
			exit(0);
			break;
		default:
			break;
		}
		
		if (fp != NULL)
		{
			pclose(fp);
		}
	}

	return NULL;
}
//yan-end

int main(int argc, char** argv)
{
	pthread_t keyboard_thread;

	odtone::setup_crash_handler();

	try {
		boost::asio::io_service ios;

		// declare MIH Usr available options
		po::options_description desc(odtone::mih::octet_string("MIH Usr Configuration"));
		desc.add_options()
			("help", "Display configuration options")
			(odtone::sap::kConf_Port, po::value<ushort>()->default_value(1234), "Port")
			(odtone::sap::kConf_File, po::value<std::string>()->default_value("mih_usr.conf"), "Configuration File")
			(odtone::sap::kConf_Receive_Buffer_Len, po::value<uint>()->default_value(4096), "Receive Buffer Length")
			(odtone::sap::kConf_MIH_Handover, po::value<bool>()->default_value("true"), "MIH User Handover support")
			(odtone::sap::kConf_MIHF_Ip, po::value<std::string>()->default_value("127.0.0.1"), "Local MIHF Ip")
			(odtone::sap::kConf_MIHF_Id, po::value<std::string>()->default_value("local-mihf"), "Local MIHF Id")
			(odtone::sap::kConf_MIH_SAP_id, po::value<std::string>()->default_value("user"), "User Id")
			(odtone::sap::kConf_MIHF_Local_Port, po::value<ushort>()->default_value(1025), "MIHF Local Communications Port")
			(odtone::sap::kConf_MIH_SAP_dest, po::value<std::string>()->default_value(""), "MIH message destination of MIH User");

		odtone::mih::config cfg(desc);
		cfg.parse(argc, argv, odtone::sap::kConf_File);

		if (cfg.help()) {
			std::cerr << desc << std::endl;
			return EXIT_SUCCESS;
		}

		mih_user usr(cfg, ios);

		pthread_create(&keyboard_thread, NULL, keyboard_handler, (void *) &usr);	//yan-21/02/2012

		ios.run();

	} catch(std::exception& e) {
		log_(0, "exception: ", e.what());
	}
}

// EOF ////////////////////////////////////////////////////////////////////////
